/*
 * Copyright (c) 2009, Hamish Morgan. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the University of Sussex nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package locusts.common.entities;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import locusts.lib.MathUtil;
import org.slf4j.LoggerFactory;

/**
 *
 * @author Hamish Morgan
 */
public class Actor extends Entity {

    private static final transient double DEFAULT_VX = 0;
    private static final transient double DEFAULT_VY = 0;
    private static final transient double DEFAULT_AX = 0;
    private static final transient double DEFAULT_AY = 0;
    //
    private double vx;
    private double vy;
    private double ax;
    private double ay;
    private transient int frame = (int) (Math.random() * 100);

    public Actor(int type, double x, double y, double vx,
            double vy, double ax, double ay, double orientation, double energy) {
        super(type, x, y, orientation, energy);
        setVelocity(vx, vy);
        setAcceleration(ax, ay);
        setEnergy(energy);
    }

    public Actor(int type, double x, double y, double vx, double vy, double ax,
            double ay, double orientation) {
        super(type, x, y, orientation);
        setVelocity(vx, vy);
        setAcceleration(ax, ay);
    }

    public Actor(int type, double x, double y, double vx, double vy, double ax,
            double ay) {
        super(type, x, y);
        setVelocity(vx, vy);
        setAcceleration(ax, ay);
    }

    public Actor(int type, double x, double y, double vx, double vy) {
        this(type, x, y, vx, vy, DEFAULT_AX, DEFAULT_AY);
    }

    public Actor(int type, double x, double y) {
        this(type, x, y, DEFAULT_VX, DEFAULT_VY);
    }

    public Actor(int type) {
        super(type);
        setVelocity(DEFAULT_VX, DEFAULT_VY);
        setAcceleration(DEFAULT_AX, DEFAULT_AY);
    }

    protected Actor(Actor other) {
        super(other);
        setVelocityX(other.vx);
        setVelocityY(other.vy);
        setAccelerationX(other.ax);
        setAccelerationY(other.ay);
        this.frame = other.frame;
    }

    /**
     * Serialize this object to an output stream. Since the serializated
     * objects are intended for network transfer, the floating point
     * precission is reduced.
     * 
     * @param out
     * @throws IOException
     */
    private void writeObject(final ObjectOutputStream out)
            throws IOException {

//        if(vx < -Float.MAX_VALUE) vx = -Float.MAX_VALUE;
//        else if(vx > Float.MAX_VALUE)  vx = Float.MAX_VALUE;
//        if(vy < -Float.MAX_VALUE) vy = -Float.MAX_VALUE;
//        else if(vy > Float.MAX_VALUE)  vy = Float.MAX_VALUE;
//        if(ax < -Float.MAX_VALUE) ax = -Float.MAX_VALUE;
//        else if(ax > Float.MAX_VALUE)  ax = Float.MAX_VALUE;
//        if(ay < -Float.MAX_VALUE) ay = -Float.MAX_VALUE;
//        else if(ay > Float.MAX_VALUE)  ay = Float.MAX_VALUE;
        if(vx < -Float.MAX_VALUE) vx = 0;
        else if(vx > Float.MAX_VALUE)  vx = 0;
        if(vy < -Float.MAX_VALUE) vy = 0;
        else if(vy > Float.MAX_VALUE)  vy = 0;
        if(ax < -Float.MAX_VALUE) ax = 0;
        else if(ax > Float.MAX_VALUE)  ax = 0;
        if(ay < -Float.MAX_VALUE) ay = 0;
        else if(ay > Float.MAX_VALUE)  ay = 0;

        out.writeFloat((float) vx);
        out.writeFloat((float) vy);
        out.writeFloat((float) ax);
        out.writeFloat((float) ay);
    }

    private void readObject(final ObjectInputStream in)
            throws IOException {
        try {
            setVelocityX(in.readFloat());
            setVelocityY(in.readFloat());
            setAccelerationX(in.readFloat());
            setAccelerationY(in.readFloat());
            frame = (int) (Math.random() * 100);
        } catch (IllegalArgumentException e) {
            LoggerFactory.getLogger(Actor.class).error("Oh Oh", e);
            throw e;
        }
    }

    public int getFrame() {
        return frame;
    }

    public void setAcceleration(double ax, double ay) {
        setAccelerationX(ax);
        setAccelerationY(ay);
    }

    public void setAccelerationX(double ax) {
        if (Double.isNaN(ax))
            throw new IllegalArgumentException("x acceleration is NaN");
        this.ax = ax;
    }

    public void setAccelerationY(double ay) {
        if (Double.isNaN(ay))
            throw new IllegalArgumentException("y acceleration is NaN");
        this.ay = ay;
    }

    public double getAX() {
        return ax;
    }

    public double getAY() {
        return ay;
    }

    public double getAccelerationHeading() {
        return Math.atan2(ay, ax);
    }

    public double getAccelerationRate() {
        return Math.sqrt(ax * ax + ay * ay);
    }

    public double getVelocity() {
        return Math.sqrt(vx * vx + vy * vy);
    }

    public double getVX() {
        return vx;
    }

    public double getVY() {
        return vy;
    }

    public void setVelocity(double vx, double vy) {
        setVelocityX(vx);
        setVelocityY(vy);
    }

    public void setVelocityX(double vx) {
        if (Double.isNaN(vx) || Double.isInfinite(vx))
            throw new IllegalArgumentException(
                    "X-Velocity is not a real number.");
        this.vx = vx;
    }

    public void setVelocityY(double vy) {
        if (Double.isNaN(vy) || Double.isInfinite(vy))
            throw new IllegalArgumentException(
                    "Y-Velocity is not a real number.");
        this.vy = vy;
    }
//

    public double getHeading() {
        return Math.atan2(vy, vx);
    }
//

    public double bearingTo(double x, double y) {
        return MathUtil.angleBetween(x - getX(), y - getY(), vx, vy);
    }

    public double bearingTo(Actor target) {
        return bearingTo(target.getX(), target.getY());
    }

    @Override
    public void setAlive(boolean alive) {
        super.setAlive(alive);
        if (!isAlive()) {
            ax = 0;
            ay = 0;
        }
    }

    @Override
    public String toString() {
        StringBuffer buf = new StringBuffer();
        buf.append("Position: (");
        buf.append(getX()).append(", ").append(getY());
        buf.append(") Velocity: (");
        buf.append(vx).append(", ").append(vy);
        return buf.toString();
    }

    @Override
    public Object clone() {
        return new Actor(this);
    }
}
